# This Python file uses the following encoding: utf-8
############################################################################
# Copyright (c) 2025 Li Auto Inc. and its affiliates
# Licensed under the Apache License, Version 2.0(the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
############################################################################
import os
import re
import json
from typing import Dict
from collections import deque
import math

from trace_data import (CoreData, TaskData, EventData, RunnableData, IrqData, TraceEventStr, IRQ_STATE, IrqState)
from trace_util import (TraceInfoUtils, FtraceUtils, TraceComputeUtils, RUNNABLE_ID_OFFSET, 
                        RUNNABLE_PRIORITY, IDLE_TASK_PRIORITY, EVENT_CNT_PER_TASK)
from trace_parser import VersionNumber


class FtraceProducer:
    """报文二进制数据转换为ftrace文本"""
    def __init__(self) -> None:
        self.task_dict: Dict[int, TaskData] = {}            # {task id: task数据}
        self.runnable_dict: Dict[int, RunnableData] = {}    # {runnable id: runnable trace数据}
        self.irq_dict: Dict[int, IrqData] = {}              # {irq id: irq数据}
        self.ftrace_cnt = 0                                 # 已解析的有效ftrace报文数
        self.tick_to_ns: float = 0                          # 1tick等于多少ns
        self.reactivate_task_state: Dict[int, bool] = {}    # {task id: task是否被reactivate}

    def produce_ftrace_file(self, input_dir, device_list=[]):
        """给每个device的二进制数据文件生成ftrace文本文件"""
        if os.path.isdir(input_dir) is False:
            print(f"[ERROR] input_dir '{input_dir}' does not exist.")
            return
        config_file = os.path.join(input_dir, "config.json")
        device_info = TraceInfoUtils.parse_config_file(config_file)
        if device_info is None:
            return
        if len(device_list) == 0:
            device_list = list(device_info)
        for device_name in device_list:
            if device_name not in device_info:
                print(f"[WARNING] {device_name} is not in config.json.")
                continue
            device_data = device_info[device_name]
            self.write_single_ftrace_file(device_name, device_data, input_dir)

    def write_single_ftrace_file(self, device_name, device_data, input_dir):
        """根据单个二进制trace数据文件生成ftrace文本文件"""
        # 解析下位机的配置信息
        self.task_dict, self.runnable_dict, self.irq_dict = \
            device_data["Task"], device_data["Runnable"], device_data["ISR"]
        self.tick_to_ns = 1e9 / device_data["stm_frequency"]
        trace_data_parser = TraceInfoUtils.get_trace_parser_from_device_data(device_data)
        if trace_data_parser is None:
            return
        # 获取二进制trace数据帧
        bin_file = os.path.join(input_dir, f"{device_name}_data.bin")
        if os.path.exists(bin_file) is False:
            print(f"[ERROR] '{bin_file}' does not exist.")
            return
        print(f"[INFO] Parsing '{bin_file}'...")
        with open(bin_file, "rb") as f:
            bin_data = f.read()
        bin_data = bytearray(bin_data)
        trace_data_list = trace_data_parser.get_trace_data_list(bin_data)
        # 二进制报文按时间戳递增排序
        trace_data_list = trace_data_parser.sort_trace_data_list(trace_data_list)
        # 获取ftrace文本
        print(f"[INFO] Writing {device_name}'s ftrace text...")
        ftrace_text_list = self.bin_to_ftrace(trace_data_list, trace_data_parser)
        output_file = os.path.join(input_dir, f"{device_name}_ftrace.log")
        with open(output_file, "w", encoding="utf-8") as f:
            f.write("# tracer: nop\n#\n")
            f.writelines(ftrace_text_list)
        print(f"[INFO] {device_name}'s ftrace text has been written to '{output_file}'.")

    def bin_to_ftrace(self, trace_data_list, trace_data_parser):
        """二进制trace数据转ftrace文本"""
        ftrace_text_list = []
        trace_data_parser.tick_to_ns = self.tick_to_ns
        self.idle_task_dict: Dict[int, TaskData] = {}   # {core_id: idle task data}
        for task_id, task_data in self.task_dict.items():
            # 只有idle task的优先级为0
            if task_data.priority == 0:
                self.idle_task_dict[task_data.core_id] = task_data
        for data_list in trace_data_list:
            cur_idx: int = 0
            event_len: int = data_list[cur_idx]
            if event_len > len(data_list):
                print(f"[ERROR] bin_to_ftrace(): length of the event({event_len}) is wrong.")
                continue
            event_type = data_list[cur_idx + 1]
            # 不解析ack报文
            if event_type == trace_data_parser.FTRACE_SCHED_SWITCH:
                text = self.get_sched_switch_text(event_len, data_list, trace_data_parser)
            elif event_type == trace_data_parser.FTRACE_TASK_SUSPEND:
                text = self.get_suspend_text(event_len, data_list, trace_data_parser)
            elif event_type == trace_data_parser.FTRACE_TASK_RESUME:
                text = self.get_resume_text(event_len, data_list, trace_data_parser)
            elif event_type == trace_data_parser.FTRACE_WAKE_UP:
                text = self.get_sched_wakeup_text(event_len, data_list, trace_data_parser)
            elif event_type == trace_data_parser.FTRACE_IRQ_ENTRY:
                text = self.get_irq_entry_text(event_len, data_list, trace_data_parser)
            elif event_type == trace_data_parser.FTRACE_IRQ_EXIT:
                text = self.get_irq_exit_text(event_len, data_list, trace_data_parser)
            elif event_type == trace_data_parser.FTRACE_EVENT_START:
                text = self.get_event_text(event_len, data_list, trace_data_parser)
            elif event_type == trace_data_parser.FTRACE_RUNNABLE_START:
                text = self.get_runnable_start_text(event_len, data_list, trace_data_parser)
            elif event_type == trace_data_parser.FTRACE_RUNNABLE_END:
                text = self.get_runnable_end_text(event_len, data_list, trace_data_parser)
            elif event_type == trace_data_parser.FTRACE_FUNCTION_START:
                text = self.get_function_start_text(event_len, data_list, trace_data_parser)
            elif event_type == trace_data_parser.FTRACE_FUNCTION_END:
                text = self.get_function_end_text(event_len, data_list, trace_data_parser)
            elif event_type == trace_data_parser.FTRACE_TASK_REACTIVATE:
                text = self.get_reactivate_text(event_len, data_list, trace_data_parser)
            else:
                continue
            if text != "":
                ftrace_text_list.append(text)
        return ftrace_text_list

    def get_reactivate_text(self, event_len, payload_list, trace_data_parser):
        """本事件不产生ftrace文本,用来标记task是否被reactivate"""
        result = trace_data_parser.parse_reactivate(event_len, payload_list)
        if not result:
            return ""
        priority, core_id, pid, time_stamp = result
        if pid not in self.task_dict:
            print(f"[WARNING] {pid} is not a valid task ID.")
            return ""
        self.reactivate_task_state[pid] = True

        self.ftrace_cnt += 1
        return ""

    def get_suspend_text(self, event_len, payload_list, trace_data_parser):
        result = trace_data_parser.parse_suspend(event_len, payload_list)
        if not result:
            return ""
        core_id, prev_pid, time_stamp, prev_state = result
        if prev_pid not in self.task_dict:
            print(f"[WARNING] {prev_pid} is not a valid task ID.")
            return ""
        prev_task_data = self.task_dict[prev_pid]
        prev_priority = prev_task_data.priority
        idle_task_data = self.idle_task_dict[core_id]
        # task被reactivate后的switch,修改运行状态为suspend
        if (prev_pid in self.reactivate_task_state) and (self.reactivate_task_state[prev_pid] is True):
            prev_state = trace_data_parser.TASK_STATE_SUSPEND
            self.reactivate_task_state[prev_pid] = False
        ftrace_text: str = f"{prev_task_data.name}-{prev_pid} ({prev_pid}) [{core_id}] {time_stamp * 1e-9:.9f}: \
{TraceEventStr.SCHED_SWITCH_STR.value}: prev_comm={prev_task_data.name} prev_pid={prev_pid} prev_prio={prev_priority} \
prev_state={trace_data_parser.get_task_state_text(prev_state)} ==> next_comm={idle_task_data.name} \
next_pid={idle_task_data.task_id} next_prio={idle_task_data.priority}\n"

        self.ftrace_cnt += 1
        return ftrace_text

    def get_resume_text(self, event_len, payload_list, trace_data_parser):
        result = trace_data_parser.parse_resume(event_len, payload_list)
        if not result:
            return ""
        next_prioirty, core_id, next_pid, time_stamp = result
        if next_pid not in self.task_dict:
            print(f"[WARNING] {next_pid} is not a valid task ID.")
            return ""
        next_task_data = self.task_dict[next_pid]
        idle_task_data = self.idle_task_dict[core_id]
        idle_priority = idle_task_data.priority
        idle_pid = idle_task_data.task_id
        idle_state = "R"

        ftrace_text: str = f"{idle_task_data.name}-{idle_pid} ({idle_pid}) [{core_id}] {time_stamp * 1e-9:.9f}: \
{TraceEventStr.SCHED_SWITCH_STR.value}: prev_comm={idle_task_data.name} prev_pid={idle_pid} prev_prio={idle_priority} \
prev_state={idle_state} ==> next_comm={next_task_data.name} \
next_pid={next_pid} next_prio={next_prioirty}\n"

        self.ftrace_cnt += 1
        return ftrace_text

    def get_sched_switch_text(self, event_len, payload_list, trace_data_parser):
        result = trace_data_parser.parse_sched_switch(event_len, payload_list)
        if not result:
            return ""
        next_prioirty, core_id, next_pid, time_stamp, prev_pid, prev_state = result
        if (next_pid not in self.task_dict) or (prev_pid not in self.task_dict):
            print(f"[WARNING] {next_pid} or {prev_pid} is not a valid task ID.")
            return ""
        next_task_data = self.task_dict[next_pid]
        prev_task_data = self.task_dict[prev_pid]
        prev_priority = prev_task_data.priority
        # task被reactivate后的switch,修改运行状态为suspend
        if (prev_pid in self.reactivate_task_state) and (self.reactivate_task_state[prev_pid] is True):
            prev_state = trace_data_parser.TASK_STATE_SUSPEND
            self.reactivate_task_state[prev_pid] = False

        ftrace_text: str = f"{prev_task_data.name}-{prev_pid} ({prev_pid}) [{core_id}] {time_stamp * 1e-9:.9f}: \
{TraceEventStr.SCHED_SWITCH_STR.value}: prev_comm={prev_task_data.name} prev_pid={prev_pid} prev_prio={prev_priority} \
prev_state={trace_data_parser.get_task_state_text(prev_state)} ==> next_comm={next_task_data.name} \
next_pid={next_pid} next_prio={next_prioirty}\n"

        self.ftrace_cnt += 1
        return ftrace_text

    def get_sched_wakeup_text(self, event_len, payload_list, trace_data_parser):
        result = trace_data_parser.parse_sched_wakeup(event_len, payload_list)
        if not result:
            return ""
        priority, core_id, pid, time_stamp, wakeup_pid = result
        if wakeup_pid not in self.task_dict:
            print(f"[WARNING] {wakeup_pid} is not a valid task ID.")
            return ""
        wakeup_priority: int = self.task_dict[wakeup_pid].priority

        wakeup_task_data = self.task_dict[wakeup_pid]

        ftrace_text: str = f"{self.task_dict[pid].name}-{pid} ({pid}) [{core_id}] {time_stamp * 1e-9:.9f}: \
{TraceEventStr.SCHED_WAKEUP_STR.value}: comm={wakeup_task_data.name} pid={wakeup_pid} prio={wakeup_priority} \
success=1 target_cpu={3:0>{core_id}}\n"

        self.ftrace_cnt += 1
        return ftrace_text

    def get_irq_entry_text(self, event_len, payload_list, trace_data_parser):
        result = trace_data_parser.parse_irq_entry(event_len, payload_list)
        if not result:
            return ""
        core_id, pid, time_stamp, irq_id, ori_irq_id, vector_pid = result
        if trace_data_parser.VERSION_NO == VersionNumber.VCOS_200.value:
            self.irq_dict[irq_id] = IrqData(name=f"ISR_{ori_irq_id}_core{core_id}")

        if irq_id not in self.irq_dict:
            print(f"[WARNING] {irq_id} is not a valid ISR ID.")
            return ""
        irq_data = self.irq_dict[irq_id]
        ftrace_text: str = f"{self.task_dict[pid].name}-{pid} ({pid}) [{core_id}] {time_stamp * 1e-9:.9f}: \
{TraceEventStr.IRQ_ENTRY_STR.value}: irq={irq_data.name} name={vector_pid}\n"

        self.ftrace_cnt += 1
        return ftrace_text

    def get_irq_exit_text(self, event_len, payload_list, trace_data_parser):
        result = trace_data_parser.parse_irq_exit(event_len, payload_list)
        if not result:
            return ""
        core_id, pid, time_stamp, irq_id, ori_irq_id = result
        if trace_data_parser.VERSION_NO == VersionNumber.VCOS_200.value:
            self.irq_dict[irq_id] = IrqData(name=f"ISR_{ori_irq_id}_core{core_id}")

        if irq_id not in self.irq_dict:
            print(f"[WARNING] {irq_id} is not a valid ISR ID.")
            return ""
        irq_data = self.irq_dict[irq_id]

        ftrace_text: str = f"{self.task_dict[pid].name}-{pid} ({pid}) [{core_id}] {time_stamp * 1e-9:.9f}: \
{TraceEventStr.IRQ_EXIT_STR.value}: irq={irq_data.name} ret={IRQ_STATE[IrqState.IRQ_HANDLED.value]}\n"

        self.ftrace_cnt += 1
        return ftrace_text

    def get_event_text(self, event_len, payload_list, trace_data_parser):
        result = trace_data_parser.parse_event_start(event_len, payload_list)
        if not result:
            return ""
        core_id, pid, time_stamp, event_mask = result

        ftrace_text: str = f"{self.task_dict[pid].name}-{pid} ({pid}) [{core_id}] \
{time_stamp * 1e-9:.9f}: {TraceEventStr.MARK_WRITE.value}: \
B|{pid}|event_{event_mask}\n{self.task_dict[pid].name}-{pid} ({pid}) [{core_id}] \
{time_stamp * 1e-9:.9f}: {TraceEventStr.MARK_WRITE.value}: E|{pid}|event_{event_mask}\n"

        self.ftrace_cnt += 2
        return ftrace_text

    def get_runnable_start_text(self, event_len, payload_list, trace_data_parser):
        result = trace_data_parser.parse_runnable_s(event_len, payload_list)
        if not result:
            return ""
        priority, core_id, pid, time_stamp, measurement_id = result

        if measurement_id not in self.runnable_dict:
            self.runnable_dict[measurement_id] = RunnableData(name=f"runnable_{measurement_id}")
        runnable_data = self.runnable_dict[measurement_id]

        ftrace_text: str = f"{self.task_dict[pid].name}-{pid} ({pid}) [{core_id}] {time_stamp * 1e-9:.9f}: \
{TraceEventStr.SCHED_SWITCH_STR.value}: prev_comm={self.task_dict[pid].name} prev_pid={pid} prev_prio={priority} \
prev_state=R ==> next_comm={runnable_data.name} next_pid={RUNNABLE_ID_OFFSET + measurement_id} \
next_prio={RUNNABLE_PRIORITY}\n"

        self.ftrace_cnt += 1
        return ftrace_text

    def get_runnable_end_text(self, event_len, payload_list, trace_data_parser):
        result = trace_data_parser.parse_runnable_s(event_len, payload_list)
        if not result:
            return ""
        priority, core_id, pid, time_stamp, measurement_id = result

        if measurement_id not in self.runnable_dict:
            self.runnable_dict[measurement_id] = RunnableData(name=f"runnable_{measurement_id}")
            ftrace_text: str = ""
        else:
            ftrace_text: str = f"{self.runnable_dict[measurement_id].name}-{RUNNABLE_ID_OFFSET + measurement_id} \
({pid}) [{core_id}] {time_stamp * 1e-9:.9f}: {TraceEventStr.SCHED_SWITCH_STR.value}: \
prev_comm={self.runnable_dict[measurement_id].name} prev_pid={RUNNABLE_ID_OFFSET + measurement_id} \
prev_prio={RUNNABLE_PRIORITY} prev_state=S ==> next_comm={self.task_dict[pid].name} \
next_pid={pid} next_prio={priority}\n"

        self.ftrace_cnt += 1
        return ftrace_text

    def get_function_start_text(self, event_len, payload_list, trace_data_parser):

        result = trace_data_parser.parse_function_s(event_len, payload_list, True)
        if not result:
            return ""
        core_id, pid, time_stamp, measurement_id, events = result

        ftrace_text: str = f"{self.task_dict[pid].name}-{pid} ({pid}) [{core_id}] {time_stamp * 1e-9:.9f}: \
{TraceEventStr.MARK_WRITE.value}: B|{pid}|{events}:{measurement_id}\n"

        self.ftrace_cnt += 1
        return ftrace_text

    def get_function_end_text(self, event_len, payload_list, trace_data_parser):
        result = trace_data_parser.parse_function_s(event_len, payload_list, False)
        if not result:
            return ""
        core_id, pid, time_stamp, measurement_id, events = result

        ftrace_text: str = f"{self.task_dict[pid].name}-{pid} ({pid}) [{core_id}] {time_stamp * 1e-9:.9f}: \
{TraceEventStr.MARK_WRITE.value}: E|{pid}|{measurement_id}\n"

        self.ftrace_cnt += 1
        return ftrace_text


class FtraceParser:
    """ftrace文本解析为指标数据"""

    def __init__(self) -> None:
        self.core_dict: Dict[int, CoreData] = {}  # {core id: core数据}
        self.task_dict: Dict[int, TaskData] = {}  # {task id: task数据}
        self.event_dict: Dict[int, Dict[int, EventData]] = {}  # {task id: {event id: event trace数据}}
        self.runnable_dict: Dict[int, RunnableData] = {}    # {runnable id: runnable trace数据}
        self.irq_dict: Dict[int, IrqData] = {}  # {irq id: irq数据}

    @classmethod
    def get_ftrace_line_info(cls, line: str):
        if "#" in line:
            return False, []
        line = line.strip()
        task_name = re.search(r"^(.+?)-", line).group(1).strip() if re.search(r"^(.+?)-", line) else None
        if task_name is None:
            return False, []
        pid = int(
            re.compile(r"\(([0-9]+)\)").search(line).group(1)) if re.compile(r"\(([0-9]+)\)").search(line) else None
        if pid is None:
            return False, []
        matches = re.findall(r"\[(\d+)\]", line)
        if len(matches) == 0:
            return False, []
        core_id = int(matches[0])
        time_stamp = float(re.search(r"\d+\.\d+", line).group(0).strip()) if re.search(r"\d+\.\d+", line) else None
        if time_stamp is None:
            return False, []
        event_type = re.search(r":(.*?):", line).group(1).strip() if re.search(r":(.*?):", line) else None
        if event_type is None:
            return False, []
        return True, [task_name, pid, core_id, time_stamp, event_type]

    @classmethod
    def get_ftrace_task_info(cls, ftrace_path: str, input_task_dict: Dict[str, list]):
        """从ftrace文本中,计算task的CET和DT"""
        # 检查入参
        if os.path.exists(ftrace_path) is False:
            print(f"{ftrace_path} does not exist.")
            return False, None
        for _, threshold_list in input_task_dict.items():
            if isinstance(threshold_list, list) is False or len(threshold_list) != 2:
                print("Parameter input_task_dict is illegal.")
                return False, None

        task_info_dict: Dict[str, TaskData] = {}
        task_cet_dict: Dict[str, list] = {}
        task_cet_list_dict: Dict[str, list] = {}
        task_dt_dict: Dict[str, list] = {}
        task_dt_list_dict: Dict[str, list] = {}
        irq_info_dict: Dict[str, IrqData] = {}
        irq_stack_dict = {}
        final_lines = FtraceUtils.ftrace_get_sorted_text(ftrace_path)
        for line in final_lines:
            res, info_list = cls.get_ftrace_line_info(line)
            if res is False:
                continue
            task_name, pid, core_id, time_stamp, event_type = info_list
            # ISR嵌套栈
            if core_id not in irq_stack_dict:
                irq_stack_dict[core_id] = deque()
            irq_stack = irq_stack_dict[core_id]
            if event_type == TraceEventStr.SCHED_SWITCH_STR.value:
                prev_pid = int(re.search(r"prev_pid=([^\s]+)", line).group(1).strip()) if re.search(
                    r"prev_pid=([^\s]+)", line) else None
                if prev_pid is None:
                    continue
                next_pid = int(re.search(r"next_pid=([^\s]+)", line).group(1).strip()) if re.search(
                    r"next_pid=([^\s]+)", line) else None
                if next_pid is None:
                    continue
                if (prev_pid >= RUNNABLE_ID_OFFSET) or (next_pid >= RUNNABLE_ID_OFFSET):
                    continue
                prev_task_name = re.search(r"prev_comm=([^ ]+)", line).group(1).strip() if re.search(
                    r"prev_comm=([^ ]+)", line) else None
                if prev_task_name is None:
                    continue
                prev_state = re.search(r"prev_state=([^ ]+)", line).group(1).strip() if re.search(
                    r"prev_state=([^ ]+)", line) else None
                if prev_state is None:
                    continue
                next_task_name = re.search(r"next_comm=([^ ]+)", line).group(1).strip() if re.search(
                    r"next_comm=([^ ]+)", line) else None
                if next_task_name is None:
                    continue
                # task切出
                if prev_task_name not in task_info_dict:
                    task_info_dict[prev_task_name] = TaskData()
                prev_task_data = task_info_dict[prev_task_name]
                prev_task_data.last_end_time = time_stamp
                # 累加本次的CET时间
                if prev_task_data.last_execute_time > 0:
                    prev_task_data.execute_time_cal += prev_task_data.last_end_time - prev_task_data.last_execute_time
                # task是否结束执行，若切出task的状态为block或suspend，则其结束执行
                if (prev_state == "S") or (prev_state == "D"):
                    if prev_task_data.last_start_time > 0:
                        # 在ISR中suspend
                        if len(irq_stack) > 0:
                            irq_name = irq_stack[0]
                            irq_data = irq_info_dict[irq_name]
                            prev_task_data.execute_time_cal -= \
                                (time_stamp - max(irq_data.last_execute_time, prev_task_data.last_start_time))
                        prev_task_data.execute_time_total += prev_task_data.execute_time_cal
                        prev_task_data.execute_time_max = max(prev_task_data.execute_time_max, 
                                                              prev_task_data.execute_time_cal)
                        prev_task_data.execute_count += 1
                        task_cet_list_dict.setdefault(prev_task_name, []).append(prev_task_data.execute_time_cal)
                        # CET超时时间戳
                        if (prev_task_name in input_task_dict) and (input_task_dict[prev_task_name][0] is not None) \
                            and (prev_task_data.execute_time_cal > input_task_dict[prev_task_name][0]):
                            task_cet_dict.setdefault(prev_task_name, []).append(prev_task_data.last_start_time)
                    prev_task_data.execute_time_cal = 0
                    # NOTE 实际上task结束执行后没有立刻ready，只是方便在无wake up事件时，
                    # 记录task ready后第一次running的时间
                    prev_task_data.ready_state = True

                # task切入
                if next_task_name not in task_info_dict:
                    task_info_dict[next_task_name] = TaskData()
                next_task_data = task_info_dict[next_task_name]
                next_task_data.last_execute_time = time_stamp
                # task结束执行后首次进running，等价于ready后首次进running
                if next_task_data.ready_state is True:
                    # DT
                    if next_task_data.last_start_time != 0:
                        period = time_stamp - next_task_data.last_start_time
                        next_task_data.period_total += period
                        next_task_data.period_max = max(next_task_data.period_max, period)
                        next_task_data.period_min = min(next_task_data.period_min,period) \
                            if next_task_data.period_min != 0 else period
                        next_task_data.period_count += 1
                        task_dt_list_dict.setdefault(next_task_name, []).append(period)
                        # DT超时时间戳
                        if (next_task_name in input_task_dict) and (input_task_dict[next_task_name][1] is not None) \
                            and (period > input_task_dict[next_task_name][1]):
                            task_dt_dict.setdefault(next_task_name, []).append(time_stamp)
                    # 记CET起点
                    next_task_data.last_start_time = time_stamp
                    next_task_data.ready_state = False
            elif event_type == TraceEventStr.IRQ_ENTRY_STR.value:
                irq_name = re.search(r"irq=([^\s]+)", line).group(1).strip() if re.search(r"irq=([^\s]+)",
                                                                                          line) else None
                if irq_name is None:
                    continue
                if irq_name not in irq_info_dict:
                    irq_info_dict[irq_name] = IrqData()
                irq_info_dict[irq_name].last_execute_time = time_stamp
                # ISR入栈
                irq_stack.append(irq_name)
            elif event_type == TraceEventStr.IRQ_EXIT_STR.value:
                irq_name = re.search(r"irq=([^\s]+)", line).group(1).strip() if re.search(r"irq=([^\s]+)",
                                                                                          line) else None
                if (irq_name is None) or (irq_name not in irq_info_dict):
                    continue
                irq_data = irq_info_dict[irq_name]
                if (len(irq_stack) > 0) and (irq_stack[-1] == irq_name):
                    irq_stack.pop()
                    if (len(irq_stack) == 0) and (irq_data.last_execute_time > 0) and (task_name in task_info_dict):
                        task_data = task_info_dict[task_name]
                        task_data.execute_time_cal -= \
                            time_stamp - max(irq_data.last_execute_time, task_data.last_start_time)

        ret_task_dict = {}
        if len(input_task_dict) == 0:
            for task_name in task_info_dict:
                ret_task_dict[task_name] = {
                    "cet_avg": 0,
                    "cet_max": 0,
                    "cet_std_dev": 0,  # Add standard deviation field
                    "cet_timestamp": [],  # CET超出阈值的时间点
                    "dt_avg": 0,
                    "dt_max": 0,
                    "dt_min": 0,
                    "dt_timestamp": [],  # DT超出阈值的时间点
                    "dt_list": [],
                    "PCET": [],  # Add PCET field
                    "cet_list": [],
                }
        else:
            for task_name in input_task_dict:
                ret_task_dict[task_name] = {
                    "cet_avg": 0,
                    "cet_max": 0,
                    "cet_std_dev": 0,  # Add standard deviation field
                    "cet_timestamp": [],  # CET超出阈值的时间点
                    "dt_avg": 0,
                    "dt_max": 0,
                    "dt_min": 0,
                    "dt_timestamp": [],  # DT超出阈值的时间点
                    "dt_list": [],
                    "PCET": [],  # Add PCET field
                    "cet_list": [],
                }

        for task_name, task_data in task_info_dict.items():
            if task_name not in ret_task_dict:
                continue
            ret_task_data = ret_task_dict[task_name]
            ret_task_data["cet_avg"] = task_data.execute_time_total / task_data.execute_count \
                if task_data.execute_count > 0 else 0
            ret_task_data["cet_max"] = task_data.execute_time_max
            if task_name in task_cet_dict:
                ret_task_data["cet_timestamp"] = task_cet_dict[task_name]
            ret_task_data["dt_avg"] = task_data.period_total / task_data.period_count \
                if task_data.period_count > 0 else 0
            ret_task_data["dt_max"] = task_data.period_max
            ret_task_data["dt_min"] = task_data.period_min
            if task_name in task_dt_dict:
                ret_task_data["dt_timestamp"] = task_dt_dict[task_name]
            if task_name in task_dt_list_dict:
                ret_task_data["dt_list"] = task_dt_list_dict[task_name]
            if task_name in task_cet_list_dict:
                ret_task_data["cet_list"] = task_cet_list_dict[task_name]

            # Calculate standard deviation of CET
            if task_data.execute_count > 0 and task_name in task_cet_list_dict:
                mean = ret_task_data["cet_avg"]
                variance = sum((x - mean) ** 2 for x in task_cet_list_dict[task_name]) / task_data.execute_count
                ret_task_data["cet_std_dev"] = math.sqrt(variance)

                # Calculate percentiles for CET
                sorted_times = sorted(task_cet_list_dict[task_name])
                percentiles = [99, 98, 97, 96, 95, 94, 93, 92, 91, 90]
                pcet_values = calculate_percentiles(sorted_times, percentiles)
                ret_task_data["PCET"] = [round(x * 1e6) for x in pcet_values]  # Convert to microseconds and round

        return True, ret_task_dict

    @classmethod
    def get_ftrace_irq_info(cls, ftrace_path: str, input_irq_dict: Dict[str, list]):
        """从ftrace文本中,计算ISR的CET和DT"""
        # 检查入参
        if os.path.exists(ftrace_path) is False:
            print(f"{ftrace_path} does not exist.")
            return False, None
        for _, threshold_list in input_irq_dict.items():
            if isinstance(threshold_list, list) is False or len(threshold_list) != 2:
                print("Parameter input_irq_dict is illegal.")
                return False, None

        irq_info_dict: Dict[str, IrqData] = {}
        irq_cet_dict: Dict[str, list] = {}
        irq_dt_dict: Dict[str, list] = {}
        irq_stack_dict = {}
        final_lines = FtraceUtils.ftrace_get_sorted_text(ftrace_path)
        for line in final_lines:
            res, info_list = cls.get_ftrace_line_info(line)
            if res is False:
                continue
            task_name, pid, core_id, time_stamp, event_type = info_list
            # ISR嵌套栈
            if core_id not in irq_stack_dict:
                irq_stack_dict[core_id] = deque()
            irq_stack = irq_stack_dict[core_id]

            if event_type == TraceEventStr.IRQ_ENTRY_STR.value:
                irq_name = re.search(r"irq=([^\s]+)", line).group(1).strip() if re.search(r"irq=([^\s]+)",
                                                                                          line) else None
                if irq_name is None:
                    continue
                vector_id = re.search(r"name=([^\s]+)", line).group(1).strip() if re.search(r"name=([^\s]+)",
                                                                                            line) else None
                if vector_id is None:
                    continue
                if irq_name not in irq_info_dict:
                    irq_info_dict[irq_name] = IrqData()
                irq_data = irq_info_dict[irq_name]
                # DT
                if irq_data.last_execute_time != 0:
                    period = time_stamp - irq_data.last_execute_time
                    irq_data.period_max = max(period, irq_data.period_max)
                    irq_data.period_min = min(period, irq_data.period_min) if irq_data.period_min != 0 else period
                    irq_data.period_total += period
                    irq_data.period_count += 1
                    # DT超时时间戳
                    if (irq_name in input_irq_dict) and (input_irq_dict[irq_name][1]
                                                         is not None) and (period > input_irq_dict[irq_name][1]):
                        irq_dt_dict.setdefault(irq_name, []).append(time_stamp)
                # ISR入栈
                irq_stack.append(irq_name)
                irq_data.last_execute_time = time_stamp
            elif event_type == TraceEventStr.IRQ_EXIT_STR.value:
                irq_name = re.search(r"irq=([^\s]+)", line).group(1).strip() if re.search(r"irq=([^\s]+)",
                                                                                          line) else None
                if irq_name is None:
                    continue
                if irq_name not in irq_info_dict:
                    continue
                irq_data = irq_info_dict[irq_name]
                # ISR出栈
                if (len(irq_stack) > 0) and (irq_stack[-1] == irq_name):
                    irq_stack.pop()
                    if len(irq_stack) > 0:
                        top_irq_name = irq_stack[-1]
                        irq_info_dict[top_irq_name].preemption_time += time_stamp - irq_data.last_execute_time
                # CET
                execute_time = time_stamp - irq_data.last_execute_time - irq_data.preemption_time
                irq_data.execute_time_max = max(execute_time, irq_data.execute_time_max)
                irq_data.execute_time_total += execute_time
                irq_data.execute_count += 1
                irq_data.preemption_time = 0
                # CET超时时间戳
                if (irq_name in input_irq_dict) and (input_irq_dict[irq_name][0]
                                                     is not None) and (execute_time > input_irq_dict[irq_name][0]):
                    irq_cet_dict.setdefault(irq_name, []).append(irq_data.last_execute_time)

        ret_irq_dict = {}
        if len(input_irq_dict) == 0:
            for irq_name in irq_info_dict:
                ret_irq_dict[irq_name] = {
                    "cet_avg": 0,
                    "cet_max": 0,
                    "cet_timestamp": [],  # CET超出阈值的时间点
                    "dt_avg": 0,
                    "dt_max": 0,
                    "dt_min": 0,
                    "dt_timestamp": [],  # DT超出阈值的时间点
                }
        else:
            for irq_name in input_irq_dict:
                ret_irq_dict[irq_name] = {
                    "cet_avg": 0,
                    "cet_max": 0,
                    "cet_timestamp": [],  # CET超出阈值的时间点
                    "dt_avg": 0,
                    "dt_max": 0,
                    "dt_min": 0,
                    "dt_timestamp": [],  # DT超出阈值的时间点
                }

        for irq_name, irq_data in irq_info_dict.items():
            if irq_name not in ret_irq_dict:
                continue
            ret_irq_data = ret_irq_dict[irq_name]
            ret_irq_data[
                "cet_avg"] = irq_data.execute_time_total / irq_data.execute_count if irq_data.execute_count > 0 else 0
            ret_irq_data["cet_max"] = irq_data.execute_time_max
            if irq_name in irq_cet_dict:
                ret_irq_data["cet_timestamp"] = irq_cet_dict[irq_name]
            ret_irq_data["dt_avg"] = irq_data.period_total / irq_data.period_count if irq_data.period_count > 0 else 0
            ret_irq_data["dt_max"] = irq_data.period_max
            ret_irq_data["dt_min"] = irq_data.period_min
            if irq_name in irq_dt_dict:
                ret_irq_data["dt_timestamp"] = irq_dt_dict[irq_name]
        return True, ret_irq_dict

    def parse_ftrace(self, config_path: str, input_dir: str, output_path: str):
        """按类型解析ftrace文本"""
        # 读配置文件
        if config_path == "" or os.path.exists(config_path) is False:
            print(f"[ERROR] Config file {config_path} does not exist.")
            return
        with open(config_path, "r", encoding='utf-8') as f:
            data = json.load(f)
        for k, items in data["Task"].items():
            self.task_dict[int(k)] = TaskData(name=items[0], priority=items[1])
        for k, v in data["ISR"].items():
            self.irq_dict[int(k)] = IrqData(name=v)
        # ftrace文本按时间戳递增排序
        if FtraceUtils.ftrace_text_sort(input_dir) is False:
            return
        input_path_list = [
            os.path.join(input_dir, filename)
            for filename in os.listdir(input_dir)
            if filename.startswith("ftrace_") and (filename.endswith(".log") or filename.endswith(".txt"))
        ]
        input_path_list.sort()
        for input_path in input_path_list:
            print(f"[INFO] Start to parse {input_path}")
            lines = []
            with open(input_path, "r", encoding='utf-8') as file:
                lines = file.readlines()
            for line in lines:
                res, info_list = self.get_ftrace_line_info(line)
                if res is False:
                    continue
                task_name, pid, core_id, time_stamp, event_type = info_list
                line = line.strip()

                if core_id not in self.core_dict:
                    self.core_dict[core_id] = CoreData()

                if pid not in self.event_dict:
                    self.event_dict[pid] = {}

                if event_type == TraceEventStr.SCHED_WAKEUP_STR.value:
                    self.parse_sched_wakeup(line, time_stamp, core_id)
                elif event_type == TraceEventStr.SCHED_SWITCH_STR.value:
                    self.parse_sched_switch(line, time_stamp, core_id, pid)
                elif event_type == TraceEventStr.IRQ_ENTRY_STR.value:
                    self.parse_irq_handler_entry(line, time_stamp, core_id)
                elif event_type == TraceEventStr.IRQ_EXIT_STR.value:
                    self.parse_irq_handler_exit(line, time_stamp, core_id, pid)
                elif event_type == TraceEventStr.MARK_WRITE.value:
                    self.parse_event(line, time_stamp, core_id, pid)

        # 保存计算结果
        TraceComputeUtils.write_result(output_path, self.core_dict, self.task_dict, self.runnable_dict, self.event_dict)
        print(f"[INFO] Parsing completed to {output_path}.")

    def parse_sched_wakeup(self, line: str, time_stamp: float, core_id: int):
        """task唤醒->ready"""
        comm = re.search(r"comm=([^\s]+)", line).group(1).strip() if re.search(r"comm=([^\s]+)", line) else None
        if comm is None:
            return
        wakeup_pid = int(re.search(r"pid=([^\s]+)", line).group(1).strip()) if re.search(r"pid=([^\s]+)",
                                                                                         line) else None
        if wakeup_pid is None:
            return
        priority = int(re.search(r"prio=([^\s]+)", line).group(1).strip()) if re.search(r"prio=([^\s]+)",
                                                                                        line) else None
        if priority is None:
            return

        # Ensure priority is set correctly
        if wakeup_pid not in self.task_dict:
            self.task_dict[wakeup_pid] = TaskData(name=comm, priority=priority)
        else:
            self.task_dict[wakeup_pid].priority = priority

        core_data = self.core_dict[core_id]
        if core_data.last_time_stamp != 0:
            core_data.total_time += time_stamp - core_data.last_time_stamp
        core_data.last_time_stamp = time_stamp

        # 初始化唤醒的task
        if wakeup_pid not in core_data.task_id_list:
            core_data.task_id_list.append(wakeup_pid)
        wakeup_task_data = self.task_dict[wakeup_pid]
        wakeup_task_data.ready_state = True  # 进入ready
        if wakeup_task_data.execute_time_cal != 0:
            wakeup_task_data.execute_time_max = max(wakeup_task_data.execute_time_cal,
                                                    wakeup_task_data.execute_time_max)
            wakeup_task_data.execute_time_min = min(
                wakeup_task_data.execute_time_cal, wakeup_task_data.execute_time_min
            ) if wakeup_task_data.execute_time_min != 0 else wakeup_task_data.execute_time_cal
            wakeup_task_data.execute_time_cal = 0

    def parse_sched_switch(self, line: str, time_stamp: float, core_id: int, pid: int):
        """task切换"""
        prev_pid = int(re.search(r"prev_pid=([^\s]+)", line).group(1).strip()) if re.search(r"prev_pid=([^\s]+)",
                                                                                            line) else None
        if prev_pid is None:
            return
        prev_priority = int(re.search(r"prev_prio=([^\s]+)", line).group(1).strip()) if re.search(
            r"prev_prio=([^\s]+)", line) else None
        if prev_priority is None:
            return
        prev_state = re.search(r"prev_state=([^\s]+)", line).group(1).strip() \
            if re.search(r"prev_state=([^\s]+)", line) else None
        if prev_state is None:
            return
        next_pid = int(re.search(r"next_pid=([^\s]+)", line).group(1).strip()) if re.search(r"next_pid=([^\s]+)",
                                                                                            line) else None
        if next_pid is None:
            return
        next_priority = int(re.search(r"next_prio=([^\s]+)", line).group(1).strip()) if re.search(
            r"next_prio=([^\s]+)", line) else None
        if next_priority is None:
            return

        core_data = self.core_dict[core_id]
        if core_data.last_time_stamp != 0:
            core_data.total_time += time_stamp - core_data.last_time_stamp
        core_data.last_time_stamp = time_stamp

        # task切换
        if prev_pid < RUNNABLE_ID_OFFSET and next_pid < RUNNABLE_ID_OFFSET:
            # 切入task
            if next_pid not in core_data.task_id_list:
                core_data.task_id_list.append(next_pid)
            if next_pid not in self.task_dict:
                self.task_dict[next_pid] = TaskData(name=next_pid, priority=next_priority)
            else:
                self.task_dict[next_pid].priority = next_priority  # Update priority
            if next_priority == IDLE_TASK_PRIORITY:
                core_data.idle_task_id = next_pid
            next_trace_data = self.task_dict[next_pid]
            # 从ready进running
            if next_trace_data.ready_state is True:
                next_trace_data.ready_state = False  #                           # 清除ready标志
                # 第一次后的启动才进行周期的计算
                if (next_trace_data.execute_count > 0) and (time_stamp > next_trace_data.last_start_time):
                    # 周期
                    period: float = time_stamp - next_trace_data.last_start_time
                    next_trace_data.period_max = max(next_trace_data.period_max, period)
                    next_trace_data.period_min = min(next_trace_data.period_min,
                                                     period) if next_trace_data.period_min != 0 else period
                    next_trace_data.period_total += period
                    next_trace_data.period_count += 1
                next_trace_data.last_start_time = time_stamp  # 更新从ready进running的时间
                next_trace_data.execute_count += 1  # 更新执行次数
            next_trace_data.last_execute_time = time_stamp  # 每次更新进入running态的时间

            # 切出task
            if prev_pid not in core_data.task_id_list:
                core_data.task_id_list.append(prev_pid)
            if prev_pid not in self.task_dict:
                self.task_dict[prev_pid] = TaskData(name=prev_pid, priority=prev_priority)
            else:
                self.task_dict[prev_pid].priority = prev_priority  # Update priority
            prev_task_data = self.task_dict[prev_pid]
            if (prev_task_data.last_execute_time != 0) and (time_stamp > prev_task_data.last_execute_time):
                # 运行时间
                execute_time: float = time_stamp - prev_task_data.last_execute_time
                prev_task_data.execute_time_cal = execute_time
                prev_task_data.execute_time_total += execute_time
                if prev_task_data.execute_time_max == 0:
                    prev_task_data.execute_time_max = prev_task_data.execute_time_cal
                    prev_task_data.execute_time_min = prev_task_data.execute_time_cal
            prev_task_data.last_end_time = time_stamp
            # 若task terminate,则计算GET时间
            if (prev_state != "R") and (prev_task_data.last_start_time != 0):
                get_execute_time = prev_task_data.last_end_time - prev_task_data.last_start_time
                prev_task_data.get_max = max(get_execute_time, prev_task_data.get_max)
                prev_task_data.get_min = min(
                    get_execute_time, prev_task_data.get_min
                ) if prev_task_data.get_min != 0 else get_execute_time
                prev_task_data.get_total += get_execute_time
        else:
            # runnable开始
            if next_pid >= RUNNABLE_ID_OFFSET:
                runnable_name = re.compile(r"next_comm=([^\s]+)").search(line).group(1) if re.compile(
                    r"next_comm=([^\s]+)").search(line) else None
                if runnable_name is None:
                    return
                measurement_id = next_pid - RUNNABLE_ID_OFFSET
                if measurement_id not in self.runnable_dict:
                    self.runnable_dict[measurement_id] = RunnableData(name=runnable_name)
                runnable_data = self.runnable_dict[measurement_id]
                if runnable_data.period_count == 0 and runnable_data.start_time == 0:
                    runnable_data.start_time = time_stamp
                else:
                    period: float = time_stamp - runnable_data.last_time
                    runnable_data.period_total += period
                    if runnable_data.period_time != 0:
                        jitter: float = abs(period - float(runnable_data.period_time * 1e6))
                        runnable_data.jitter_max = max(runnable_data.jitter_max, jitter)
                        runnable_data.jitter_total += jitter
                    runnable_data.period_count += 1
                runnable_data.last_time = time_stamp
            # runnable结束
            elif prev_pid >= RUNNABLE_ID_OFFSET:
                runnable_name = re.compile(r"prev_comm=([^\s]+)").search(line).group(1) if re.compile(
                    r"prev_comm=([^\s]+)").search(line) else None
                if runnable_name is None:
                    return
                measurement_id = prev_pid - RUNNABLE_ID_OFFSET
                if measurement_id not in self.runnable_dict:
                    self.runnable_dict[measurement_id] = RunnableData(name=runnable_name)

    def parse_irq_handler_entry(self, line: str, time_stamp: float, core_id: int):
        """进中断"""
        irq_name = re.search(r"irq=([^\s]+)", line).group(1).strip() if re.search(r"irq=([^\s]+)", line) else None
        if irq_name is None:
            return
        name = re.search(r"name=([^\s]+)", line).group(1).strip() if re.search(r"name=([^\s]+)", line) else None
        if name is None:
            return

        core_data = self.core_dict[core_id]
        if core_data.last_time_stamp != 0:
            core_data.total_time += time_stamp - core_data.last_time_stamp
        core_data.last_time_stamp = time_stamp

        irq_id = -1
        for cur_id, irq_data in self.irq_dict:
            if irq_data.name == irq_name:
                irq_id = cur_id
                break
        if irq_id not in self.irq_dict:
            return
        irq_data = self.irq_dict[irq_id]
        if (irq_data.last_execute_time != 0) and (time_stamp > irq_data.last_execute_time):
            # irq周期
            period: float = time_stamp - irq_data.last_execute_time
            irq_data.period_max = max(period, irq_data.period_max)
            irq_data.period_min = min(period, irq_data.period_min) if irq_data.period_min != 0 else period
            irq_data.period_total += period
            irq_data.period_count += 1
        irq_data.last_execute_time = time_stamp

    def parse_irq_handler_exit(self, line: str, time_stamp: float, core_id: int, pid: int):
        """出中断"""
        irq_name = re.search(r"irq=([^\s]+)", line).group(1).strip() if re.search(r"irq=([^\s]+)", line) else None
        if not irq_name:
            return

        core_data = self.core_dict[core_id]
        if core_data.last_time_stamp != 0:
            core_data.total_time += time_stamp - core_data.last_time_stamp
        core_data.last_time_stamp = time_stamp

        irq_id = -1
        for cur_id, irq_data in self.irq_dict:
            if irq_data.name == irq_name:
                irq_id = cur_id
                break
        if irq_id not in self.irq_dict:
            return
        irq_data = self.irq_dict[irq_id]
        if (irq_data.last_execute_time != 0) and (time_stamp > irq_data.last_execute_time):
            # irq的运行时间
            execute_time: float = time_stamp - irq_data.last_execute_time
            irq_data.execute_time_max = max(execute_time, irq_data.execute_time_max)
            irq_data.execute_time_min = min(
                execute_time, irq_data.execute_time_min) if irq_data.execute_time_min != 0 else execute_time
            irq_data.execute_time_total += execute_time
            irq_data.execute_count += 1
            # 记录task运行期间所有irq的运行时间
            if pid in self.task_dict and self.task_dict[pid].priority == IDLE_TASK_PRIORITY:
                core_data.irq_in_idle_time += execute_time
                self.task_dict[pid].irq_time += execute_time

    def parse_event(self, line: str, time_stamp: float, core_id: int, pid: int):
        """触发event"""
        # event只解析触发即可
        if ": B|" not in line:
            return
        event_mask = int(
            re.compile(r"event_(\d+)").search(line).group(1)) if re.compile(r"event_(\d+)").search(line) else None
        if event_mask is None:
            return

        core_data = self.core_dict[core_id]
        if core_data.last_time_stamp != 0:
            core_data.total_time += time_stamp - core_data.last_time_stamp
        core_data.last_time_stamp = time_stamp

        # 遍历每个event
        for idx in range(EVENT_CNT_PER_TASK):
            if (event_mask >> idx) & 0x01:
                if idx not in self.event_dict[pid]:
                    self.event_dict[pid][idx] = EventData(name=f"event_{idx}",
                                                          start_time=time_stamp,
                                                          last_time=time_stamp)
                else:
                    event_data = self.event_dict[pid][idx]
                    period: float = time_stamp - event_data.last_time
                    if event_data.period_time != 0:
                        jitter: float = abs(period - float(event_data.period_time * 1e6))
                        event_data.jitter_max = max(event_data.jitter_max, jitter)
                        event_data.jitter_total += jitter
                    event_data.period_total += period
                    event_data.period_count += 1
                    event_data.last_time = time_stamp

def calculate_percentiles(data, percentiles):
    """Calculate the specified percentiles of a list of numbers."""
    data.sort()
    results = []
    for percentile in percentiles:
        k = (len(data) - 1) * (percentile / 100)
        f = math.floor(k)
        c = math.ceil(k)
        if f == c:
            results.append(data[int(k)])
        else:
            d0 = data[int(f)] * (c - k)
            d1 = data[int(c)] * (k - f)
            results.append(d0 + d1)
    return results
